'''
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http:#www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
'''
import copy

# import java.math.BigDecimal
# import java.util.Arrays

# import org.apache.jena.self.datatypes.xsd.impl.XSDAbstractDateTimeType


'''
 * Represent an XSD duration value. We use a seven dimensional space
 * with years, months, days, hours, minutes, seconds and fractional seconds.
 * This deviates from the spec which allows arbitrary position
 * decimals for seconds.
'''

from base.datatypes.xsd.AbstractDateTime import AbstractDateTime
from base.java.StringBuilder import StringBuilder
from base.datatypes.xsd.impl.XSDAbstractDateTimeType import XSDAbstractDateTimeType

class XSDDuration(AbstractDateTime):

    '''
     * Constructor - should only be used by the internals but # param
     * scope because
     * the internals spread across multiple packages.
     *
     * @param value the date/time value returned by the parsing
    '''
    # param
    # return
    def __init__(self, value):
        super().__init__(value)
    

    '''
     * Return the number of years in the duration
    '''
    # param
    # return int 
    def getYears(self):
        global CY
        return self.data[CY]
    

    '''
     * Return the number of months in the duration
    '''
    # param
    # return int 
    def getMonths(self):
        global M
        return self.data[M]
    

    '''
     * Return the number of years in the duration
    '''
    # param
    # return int
    def getDays(self):
        global D
        return self.data[D]
    

    '''
     * Return the number of hours in the duration
    '''
    # param
    # return int
    def getHours(self):
        global h
        return self.data[h]
    

    '''
     * Return the number of minutes in the duration
    '''
    # param
    # return int
    def getMinutes(self):
        global m
        return self.data[m]
    

    '''
     * Return the number of full seconds in the duration
    '''
    # param
    # return int
    def getFullSeconds(self):
        return self.data[s]
    

    '''
     * Return the number of seconds in the duration, including fractional part
    '''
    # param
    # return double
    def getSeconds(self):
        return self.data[s] + super().fractionalSeconds
    

    '''
     * Return the number of seconds in the duration, including the fractional part,
     * in a lossless but expensive notation - i.e. a BigDecimal.
    '''
    # param
    # return BigDecimal 
    def getBigSeconds(self):
        # global ms
        # global msscale
        # global s
        # return BigDecimal.valueOf(self.data[ms], self.data[msscale])
        #         .add(BigDecimal.valueOf(self.data[s]))
        pass
    

    '''
     * Return the time component of the duration - i.e. just the hours/mins/seconds,
     * and returns the values in seconds.
    '''
    # param
    # return double
    def getTimePart(self):
        global h
        global m
        return ((self.data[h]) * 60 + self.data[m]) * 60 + self.getSeconds()
    

    '''
     * Serializer
    '''
    # @Override
    # param
    # return String
    def toString(self):
        global CY
        global M
        global D
        global h
        global m
        global s
        global ms
        global msscale
        # All zeros -> return canonical zero duration.
        if (self.data[CY] == 0 and self.data[M] == 0 and self.data[D] == 0 and self.data[h] == 0 and self.data[m] == 0 and self.data[s] == 0 and self.data[ms] == 0):
            return "PT0S"

        # StringBuilder
        message = StringBuilder()
        # int 
        negate = 1
        if (self.data[CY] < 0 or self.data[M] < 0 or self.data[D] < 0 or self.data[h] < 0 or self.data[m] < 0 or self.data[s] < 0 or self.data[ms] < 0):
            message.append('-')
            negate = -1
        
        # All zeros -> return canonical zero duration.
        if (self.data[CY] == 0 and self.data[M] == 0 and self.data[D] == 0 and self.data[h] == 0 and self.data[m] == 0 and self.data[s] == 0 and self.data[ms] == 0):
            return "PT0S"
        message.append('P')
        if (self.data[CY] != 0):
            message.append(negate * self.data[CY])
            message.append('Y')
        
        if (self.data[M] != 0):
            message.append(negate * self.data[M])
            message.append('M')
        
        if (self.data[D] != 0):
            message.append(negate * self.data[D])
            message.append('D')
        
        if (self.data[h] != 0 or self.data[m] != 0 or self.data[s] != 0 or self.data[ms] != 0):
            message.append('T')
            if (self.data[h] != 0):
                message.append(negate * self.data[h])
                message.append('H')
            
            if (self.data[m] != 0):
                message.append(negate * self.data[m])
                message.append('M')
            
            if (self.data[s] != 0 or self.data[ms] != 0):
                message.append(negate * self.data[s])
                if (self.data[ms] != 0):
                    message.append('.')
                    XSDAbstractDateTimeType.appendFractionalTime(message, negate * self.data[ms], self.data[msscale])
                
                message.append('S')
            
        

        return message.toString()
    

    # The following duration comparison code is based on Xerces DurationDV, Apache Software Foundation

    # order-relation on duration is a partial order. The dates below are used to
    # for comparison of 2 durations, based on the fact that
    # duration x and y is x<=y iff s+x<=s+y
    # see 3.2.6 duration W3C schema self.datatype specs
    #
    # the dates are in format::CCYY,MM,DD, H, S, M, MS, timezone
    # param
    # return
    # private final int[][] 
    DATETIMES = [
           [1696, 9, 1, 0, 0, 0, 0, 'Z'],
           [1697, 2, 1, 0, 0, 0, 0, 'Z'],
           [1903, 3, 1, 0, 0, 0, 0, 'Z'],
           [1903, 7, 1, 0, 0, 0, 0, 'Z']
           ]

    # param
    # return
    # private int[][] 
    fDuration = None

    '''
     * Compares 2 given durations. (refer to W3C Schema self.datatypes "3.2.6 duration")
     *
     * @param date1  Unnormalized duration
     * @param date2  Unnormalized duration
     * @param strict (min/max)Exclusive strict == True ( LESS_THAN ) or ( GREATER_THAN )
     *               (min/max)Inclusive strict == False (LESS_EQUAL) or (GREATER_EQUAL)
     * @return INDETERMINATE if the order relationship between date1 and date2 is indeterminate.
     * EQUAL if the order relation between date1 and date2 is EQUAL.
     * If the strict parameter is True, return LESS_THAN if date1 is less than date2 and
     * return GREATER_THAN if date1 is greater than date2.
     * If the strict parameter is False, return LESS_THAN if date1 is less than OR equal to date2 and
     * return GREATER_THAN if date1 is greater than OR equal to date2
    '''
    # @Override
    # param int[] int[] boolean
    # return short
    # protected
    def compareValues(self, date1, date2, strict):

        date1 = self.canonical(date1)
        date2 = self.canonical(date2)

        #REVISIT: this is unoptimazed vs of comparing 2 durations
        #         Algorithm is described in 3.2.6.2 W3C Schema self.datatype specs
        #

        #add constA to both durations
        # short
        resultA = super().INDETERMINATE
        resultB = resultA

        #try and see if the objects are equal
        resultA = self.compareOrder(date1, date2)
        # short
        baseResult = resultA   # Full comparison including time fractions
        if (resultA == 0):
            return 0

        global fDuration
        if fDuration is None:
            # fDuration = new int[2][TOTAL_SIZE]
            fDuration = []
        
        #long comparison algorithm is required
        # 两个 int[] 
        tempA = self.addDuration(date1, 0, fDuration[0])
        tempB = self.addDuration(date2, 0, fDuration[1])
        resultA = self.compareOrder(tempA, tempB)
        if (resultA == super().INDETERMINATE):
            return super().INDETERMINATE
        

        tempA = self.addDuration(date1, 1, fDuration[0])
        tempB = self.addDuration(date2, 1, fDuration[1])
        resultB = self.compareOrder(tempA, tempB)
        resultA = self.compareResults(resultA, resultB, strict)
        if (resultA == super().INDETERMINATE):
            return super().INDETERMINATE
        

        tempA = self.addDuration(date1, 2, fDuration[0])
        tempB = self.addDuration(date2, 2, fDuration[1])
        resultB = self.compareOrder(tempA, tempB)
        resultA = self.compareResults(resultA, resultB, strict)
        if (resultA == super().INDETERMINATE):
            return super().INDETERMINATE
        

        tempA = self.addDuration(date1, 3, fDuration[0])
        tempB = self.addDuration(date2, 3, fDuration[1])
        resultB = self.compareOrder(tempA, tempB)
        resultA = self.compareResults(resultA, resultB, strict)

        if (resultA == 0):
            # determinate equality for self.data portion, so base comparison
            # (which includes fractional time) is the correct result
            return baseResult
        
        return resultA
    

    # param short short v
    # return short
    # private
    def compareResults(self, resultA, resultB, strict):
        if (resultB == super().INDETERMINATE):
            return super().INDETERMINATE
        elif (resultA != resultB and strict):
            return super().INDETERMINATE
        elif (resultA != resultB and not strict):
            if (resultA != 0 and resultB != 0):
                return super().INDETERMINATE
            else:
                return resultA if (resultA != 0) else resultB
            
        
        return resultA
    

    '''
     * Equality function (value based).
    '''
    # @Override
    # param Object
    # return boolean
    def equals(self, obj):
        if (self == obj):
            return True
        if (obj is None):
            return False
        if isinstance(obj, XSDDuration):
            # XSDDuration
            dur = obj
            # 两个 int[] 
            self.data1 = self.canonical(self.data)
            self.data2 = self.canonical(dur.data)
            for i in range(0, self.data1.length):
                if (self.data1[i] != self.data2[i]):
                    return False
            
            return True
        
        return super().equals(obj)
    

    # @Override
    # param
    # return int 
    def hashCode(self):
        # int[]
        self.data1 = self.canonical(self.data)
        # int
        hash = 1816
        for aData in self.data1:
            hash = (hash << 1) ^ aData
        # for (int a.data : self.data1)
        #     hash = (hash << 1) ^ aData
        return hash
    

    # param int[] int int[]
    # return int[]
    # private
    def addDuration(self, date, index, duration):
        #REVISIT: some code could be shared between normalize() and this method,
        #         however is it worth moving it? The structures are different...
        #

        self.resetDateObj(duration)
        #add months (may be modified additionaly below)
        # int
        temp = self.DATETIMES[index][M] + date[M]
        duration[M] = self.modulo(temp, 1, 13)
        # int
        carry = self.fQuotient(temp, 1, 13)

        #add years (may be modified additionaly below)
        duration[CY] = self.DATETIMES[index][CY] + date[CY] + carry

        #add seconds
        temp = self.DATETIMES[index][s] + date[s]
        carry = self.fQuotient(temp, 60)
        duration[s] = self.mod(temp, 60, carry)

        #add minutes
        temp = self.DATETIMES[index][m] + date[m] + carry
        carry = self.fQuotient(temp, 60)
        duration[m] = self.mod(temp, 60, carry)

        #add hours
        temp = self.DATETIMES[index][h] + date[h] + carry
        carry = self.fQuotient(temp, 24)
        duration[h] = self.mod(temp, 24, carry)


        duration[D] = self.DATETIMES[index][D] + date[D] + carry

        while (True):

            temp = self.maxDayInMonthFor(duration[CY], duration[M])
            if (duration[D] < 1): #original duration was negative
                duration[D] = duration[D] + self.maxDayInMonthFor(duration[CY], duration[M] - 1)
                carry = -1
            elif (duration[D] > temp):
                duration[D] = duration[D] - temp
                carry = 1
            else:
                break
            
            temp = duration[M] + carry
            duration[M] = self.modulo(temp, 1, 13)
            duration[CY] = duration[CY] + self.fQuotient(temp, 1, 13)
        
        global utc
        duration[utc] = 'Z'
        return duration
    


    # XXX Signedness?
    # Day-time, year-month canonicalization. 
    # param
    # return int[]
    # private
    def canonical(self, val):
        val = [copy.deepcopy(x) for x in val]
        # val = Arrays.copyOf(val, val.length)

        while (val[ms] >= 1000):
            val[s] += 1
            val[ms] -= 1000
        
        while (val[ms] <= -1000):
            val[s] -= 1
            val[ms] += 1000
        

        while (val[s] >= 60):
            val[m] += 1
            val[s] -= 60
        
        while (val[s] <= -60):
            val[m] -= 1
            val[s] += 60
        


        while (val[m] >= 60):
            val[h] += 1
            val[m] -= 60
        
        while (val[m] <= -60):
            val[h] -= 1
            val[m] += 60
        

        while (val[h] >= 24):
            val[D] += 1
            val[h] -= 24
        
        while (val[h] <= -24):
            val[D] -= 1
            val[h] += 24
        

        while (val[M] >= 12):
            val[CY] += 1
            val[M] -= 12
        
        while (val[M] <= -12):
            val[CY] -= 1
            val[M] += 12
        

        return val
    

